package org.femto.math.vis;

import org.femto.math.linalg.*;
import org.femto.math.Util;

public class TrackballHyperbolic {

    private int width, height;

    /* In standard XY coordinate plane */
    private int x, y;
    private int x0, y0;
    private int dx, dy;

    /* Trackball */
    private Quat r;
    private Quat incr;
    private Vec3 centroid;
    private double scale;

    /* Zoom */
    private double zoomCoeff;
    private double zoomFactor = 1.0;

    /* Pan */
    private double panCoeff;
    private double panX = 0.0;
    private double panY = 0.0;

    private enum STATE {
        NOTHING,
        TRACKBALL,
        ZOOM,
        PAN
    }

    private STATE state = STATE.NOTHING;

    private int activateButtonTrackball;
    private int activateButtonZoom;
    private int activateButtonPan;

    private int activateModifierTrackball;
    private int activateModifierZoom;
    private int activateModifierPan;

    private boolean active;

    public TrackballHyperbolic(double panCoeff, double zoomCoeff) {
        state = STATE.NOTHING;

        activateButtonTrackball = java.awt.event.MouseEvent.BUTTON1;
        activateButtonZoom = java.awt.event.MouseEvent.BUTTON3;
        activateButtonPan = java.awt.event.MouseEvent.BUTTON1;

        activateModifierTrackball = java.awt.event.MouseEvent.BUTTON1_MASK;
        activateModifierZoom = 0;
        activateModifierPan = java.awt.event.MouseEvent.BUTTON1_MASK | java.awt.event.MouseEvent.CTRL_MASK;

        r = new Quat(0.0, 1.0, 0.0, 0.0);
        
        centroid = new Vec3(0.0, 0.0, 0.0);
        incr = new Quat();
        scale = 1.0;

        this.panCoeff = panCoeff;
        this.zoomCoeff = zoomCoeff;
    }

    public boolean isActive() {
        return active;
    }

    public Matrix4x4 getTransform() {

        Matrix4x4 trans = new Matrix4x4();
        Matrix4x4 invTrans = new Matrix4x4();

        trans.setTranslation(centroid);
        invTrans.setTranslation(centroid.scale(-1));

        Matrix4x4 rotation = r.toMatrix4x4();

        Matrix4x4 zoom = new Matrix4x4();
        zoom.setScalingUniform(Math.abs(zoomFactor));

        Matrix4x4 pan = new Matrix4x4();
        pan.setTranslation(panX, panY, 0);

        //return Matrix4x4.mul(pan, Matrix4x4.mul(zoom, Matrix4x4.mul(trans, Matrix4x4.mul(rotation, invTrans))));
        return Matrix4x4.mul(Matrix4x4.mul(zoom, rotation), pan);
    }

    public void update() {
        //System.out.println("x= " + x + " y=" + y + " dx= " + dx + " dy=" + dy);

        if(state == STATE.TRACKBALL) {
            Vec3 a = new Vec3(Util.remap(x, 0, width, -1, 1), Util.remap(y, 0, height, -1, 1), 0);
            Vec3 b = new Vec3(Util.remap(x+dx, 0, width, -1, 1), Util.remap(y+dy, 0, height, -1, 1), 0);

            a.v[2] = projectOntoSphere(scale, a.v[0], a.v[1]);
            b.v[2] = projectOntoSphere(scale, b.v[0], b.v[1]);

            Vec3 axis = Vec3.cross(a, b);
            axis.normalizeSelf();
            Vec3 c = Vec3.sub(a,b);

            double t = Util.clamp(c.norm()/(2.0*scale), -1.0, 1.0);
            double phi = 2.0 * Math.asin(t);

            incr.setFormAxisRadians(axis, phi);
            r = Quat.mul(r, incr);
        }
        else if(state == STATE.ZOOM) {
            zoomFactor += zoomCoeff*dy;
        }
        else if(state == STATE.PAN) {
            panX += dx*panCoeff;
            panY += dy*panCoeff;
        }
    }

    private double projectOntoSphere(double r, double x, double y) {
        double d, t, z;

        d = Math.sqrt(x * x + y * y);
        if (d < r * 0.70710678118654752440) {    /* Inside sphere */
            z = Math.sqrt(r * r - d * d);
        } else {           /* On hyperbola */
            t = r / 1.41421356237309504880;
            z = t * t / d;
        }
        return z;
    }

    public void reshape(int widthPixels, int heightPixels) {
        width = widthPixels;
        height = heightPixels;
    }

    public void mouse(int cx, int cy, int button, int modifier, boolean depressed) {
        // handle the Trackball
        if(button == activateButtonTrackball && depressed && modifier == activateModifierTrackball) {
            state = STATE.TRACKBALL;
            x = x0 = cx;
            y = y0 = cy;
            dx = dy = 0;
        }
        else if(button == activateButtonTrackball && !depressed) {
            if(dx == 0 && dy == 0) {
                update();
            }
            state = STATE.NOTHING;
            dx = dy = 0;
        }
        // handle the Zoom
        else if(button == activateButtonZoom && depressed) {
            state = STATE.ZOOM;
            x = x0 = cx;
            y = y0 = cy;
            dx = dy = 0;
        }
        else if(button == activateButtonZoom && !depressed) {
            if(dx == 0 && dy == 0) {
                update();
            }
            state = STATE.NOTHING;
            dx = dy = 0;
        }
        // handle Pan
        else if(button == activateButtonPan && depressed && modifier == activateModifierPan) {
            state = STATE.PAN;
            x = x0 = cx;
            y = y0 = cy;
            dx = dy = 0;
        }
        else if(button == activateButtonZoom && !depressed) {
            if(dx == 0 && dy == 0) {
                update();
            }
            state = STATE.PAN;
            dx = dy = 0;
        }
    }

    public void motion(int cx, int cy) {
        if(state != STATE.NOTHING) {
            dx = cx - x;
            dy = cy - y;
            x = cx;
            y = cy;
            update();
        }
    }

    public void idle() {
        r = Quat.mul(r, incr);
    }

    public String getStateName() {
       return state.toString();
    }

}
